package com.jwater.core.utils;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jwater.org.apache.tools.zip.ZipEntry;
import jwater.org.apache.tools.zip.ZipFile;
import jwater.org.apache.tools.zip.ZipOutputStream;

/**
 * @description:
 * @author: jnner
 * @createDate: 2021/2/5
 * @version: 1.0
 */
public class ZipUtils {
    protected static Logger logger = LoggerFactory.getLogger(ZipUtils.class);

    private static final byte[] BUFFER = new byte[4096 * 1024];

    public static boolean mergeSkip = true;

    public static class ZipInStream extends java.util.zip.ZipInputStream {
        public ZipInStream(InputStream inputStream) {
            super(inputStream);
        }
    }

    /**
     * @param srcZipFiles
     * @param copy
     * @throws IOException
     */
    public static void mergeZipFiles(List<File> srcZipFiles, File copy, Set<String> skipItems, File appendZipFile)
            throws IOException {
        if (copy.getParentFile() != null && !copy.getParentFile().exists()) {
            copy.getParentFile().mkdirs();
        }
        Set<String> existItems = new HashSet<String>();
        if (mergeSkip) {
            existItems.addAll(skipItems);
        }
        ZipOutputStream out = new ZipOutputStream(copy);
        byte[] ch = new byte[1024];
        for (File srcZipFile : srcZipFiles) {
            if (!srcZipFile.exists()) {
                logger.error("mergeZipFiles file not exist:" + srcZipFile);
                continue;
            }
            ZipInStream zins = new ZipInStream(new FileInputStream(srcZipFile));
            java.util.zip.ZipEntry zipEntry = null;
            while ((zipEntry = zins.getNextEntry()) != null) {
                if (!existItems.contains(zipEntry.getName())) {
                    if (mergeSkip) {
                        existItems.add(zipEntry.getName());
                    }
                    out.putNextEntry(new ZipEntry(zipEntry.getName()));
                    if (!zipEntry.isDirectory()) {
                        int i;
                        while ((i = zins.read(ch)) != -1) {
                            out.write(ch, 0, i);
                        }
                        zins.closeEntry();
                    }
                    out.closeEntry();
                }
            }
            zins.close();
        }
        if (appendZipFile != null && appendZipFile.exists()) {
            ZipInStream zins = new ZipInStream(new FileInputStream(appendZipFile));
            java.util.zip.ZipEntry zipEntry = null;
            while ((zipEntry = zins.getNextEntry()) != null) {
                out.putNextEntry(new ZipEntry(zipEntry.getName()));
                if (!zipEntry.isDirectory()) {
                    int i;
                    while ((i = zins.read(ch)) != -1) {
                        out.write(ch, 0, i);
                    }
                    zins.closeEntry();
                }
                out.closeEntry();
            }
            zins.close();
        }
        out.close();
    }

    /**
     * @param srcZipFile
     * @param copy
     * @param input
     * @throws IOException
     */
    public static void copyAndPutFile(File srcZipFile, File copy, File input) throws IOException {
        copyAndPutFile(srcZipFile, copy, input, true);
    }

    /**
     * @param srcZipFile
     * @param copy
     * @param input
     * @param isZipRoot
     * @throws IOException
     */
    public static void copyAndPutFile(File srcZipFile, File copy, File input, boolean isZipRoot) throws IOException {
        if (srcZipFile.exists() && input.exists()) {
            ZipFile zipFile = new ZipFile(srcZipFile);
            ZipOutputStream out = new ZipOutputStream(copy);
            Enumeration<? extends ZipEntry> entries = zipFile.getEntries();
            while (entries.hasMoreElements()) {
                ZipEntry zipEntry = entries.nextElement();
                out.putNextEntry(zipEntry);
                if (!zipEntry.isDirectory()) {
                    copy(zipFile.getInputStream(zipEntry), out);
                }
                out.closeEntry();
            }
            if (isZipRoot || input.isFile()) {
                zip(out, input, input.getName());
            } else {
                File[] files = input.listFiles();
                for (File file : files) {
                    zip(out, file, file.getName());
                }
            }
            out.close();
        }
    }

    private static void copy(InputStream input, OutputStream output) throws IOException {
        int bytesRead;
        while ((bytesRead = input.read(BUFFER)) != -1) {
            output.write(BUFFER, 0, bytesRead);
        }
    }

    /**
     * 压缩zip文件
     *
     * @param outZipFile
     * @param input
     * @throws Exception
     */
    public static void zip(File outZipFile, File input) throws IOException {
        zip(new FileOutputStream(outZipFile), input);
    }

    public static void zip(File outZipFile, File input, boolean isZipRoot) throws IOException {
        zip(new FileOutputStream(outZipFile), input, isZipRoot);
    }

    /**
     * 压缩zip文件
     *
     * @param outZipFile
     * @param input
     * @throws IOException
     */
    public static void zip(OutputStream outZipFile, File input) throws IOException {
        zip(outZipFile, input, true);
    }

    public static void zip(OutputStream outZipFile, File input, boolean isZipRoot) throws IOException {
        ZipOutputStream out = new ZipOutputStream(outZipFile);
        if (isZipRoot || input.isFile()) {
            zip(out, input, input.getName());
        } else {
            File[] files = input.listFiles();
            for (File file : files) {
                zip(out, file, file.getName());
            }
        }
        out.close();
    }

    private static void zip(ZipOutputStream out, File f, String base) throws IOException {
        if (f.isDirectory()) {
            File[] fl = f.listFiles();
            if (fl.length == 0) {
                out.putNextEntry(new ZipEntry(base + "/"));
            }
            for (int i = 0; i < fl.length; i++) {
                zip(out, fl[i], base + "/" + fl[i].getName());
            }
        } else {
            out.putNextEntry(new ZipEntry(base));
            FileInputStream in = new FileInputStream(f);
            BufferedInputStream bi = new BufferedInputStream(in);
            int i;
            byte[] ch = new byte[1024];
            while ((i = bi.read(ch)) != -1) {
                out.write(ch, 0, i);
            }
            bi.close();
            in.close();
        }
    }

    /**
     * 解压zip文件
     *
     * @param inputZipFile
     * @param outDir
     * @throws IOException
     */
    public static void unzip(File inputZipFile, File outDir) throws IOException {
        unzip(new FileInputStream(inputZipFile), outDir);
    }

    /**
     * 解压zip文件
     *
     * @param inputZipFile
     * @param outDir
     * @throws IOException
     */
    public static void unzip(InputStream inputZipFile, File outDir) throws IOException {
        ZipInStream zins = new ZipInStream(inputZipFile);
        java.util.zip.ZipEntry ze = null;
        byte[] ch = new byte[1024];
        while ((ze = zins.getNextEntry()) != null) {
            File zfile = new File(outDir, ze.getName());
            File fpath = new File(zfile.getParentFile().getPath());
            if (ze.isDirectory()) {
                if (!zfile.exists())
                    zfile.mkdirs();
                zins.closeEntry();
            } else {
                if (!fpath.exists())
                    fpath.mkdirs();
                FileOutputStream fouts = new FileOutputStream(zfile);
                int i;
                while ((i = zins.read(ch)) != -1)
                    fouts.write(ch, 0, i);
                zins.closeEntry();
                fouts.close();
            }
        }
        inputZipFile.close();
        zins.close();
    }

    /**
     * 获取压缩包根目录名称
     *
     * @param inputZipFile
     * @return
     * @throws IOException
     */
    public static String getRootName(File inputZipFile) throws IOException {
        return getRootName(new FileInputStream(inputZipFile));
    }

    /**
     * 获取压缩包根目录名称
     *
     * @param inputZipFile
     * @return
     * @throws IOException
     */
    public static String getRootName(InputStream inputZipFile) throws IOException {
        ZipInStream zins = new ZipInStream(inputZipFile);
        java.util.zip.ZipEntry ze = null;
        Set<String> set = new HashSet<String>();
        try {
            while ((ze = zins.getNextEntry()) != null) {
                set.add(firstStr(ze.getName()));
                if (set.size() > 1) {
                    return null;
                }
            }
            if (set.size() == 1) {
                for (String root : set) {
                    return root;
                }
            }
        } finally {
            inputZipFile.close();
            zins.close();
        }
        return null;
    }

    protected static String firstStr(String value) {
        int index = value.indexOf("/");
        if (index >= 0) {
            return value.substring(0, index);
        }
        return value;
    }

    /**
     * 获取zip包里的目录名称
     *
     * @param zipFile
     * @return
     */
    public static List<String> getInnerZipName(File zipFile) {
        try {
            return getInnerZipName(new FileInputStream(zipFile));
        } catch (FileNotFoundException e) {
            logger.error(e.toString(), e);
        }
        return new ArrayList<String>();
    }

    /**
     * 获取zip包里的目录名称
     *
     * @param inputZipFile
     * @return
     */
    public static List<String> getInnerZipName(InputStream inputZipFile) {
        List<String> list = new ArrayList<String>();
        try {
            ZipInStream zins = new ZipInStream(inputZipFile);
            java.util.zip.ZipEntry ze = null;
            try {
                while ((ze = zins.getNextEntry()) != null) {
                    if (ze.getName().endsWith(".zip") && ze.getName().split("/").length == 1) {
                        list.add(ze.getName());
                    }
                }
            } finally {
                inputZipFile.close();
                zins.close();
            }
        } catch (Exception e) {
            logger.error(e.toString(), e);
        }
        return list;
    }
}